Link to this headingSecure Remote Password Protocol (SRP)

  • Used in Apple’s iCloud Key Vault and MikroTik Authentication
  • Does not store the password in plaintext

Link to this headingSecurity

SRP is vulnerable to pre-computation attacks, due to the fact that it hands over the user’s “salt” to any attacker who can start an SRP session. This means I can ask a server for your salt, and build a dictionary of potential password hashes even before the server is compromised.

Link to this headingSubgroup Attack

ERROR: You cannot safely use standard Diffie-Hellman groups with SRP! Doing multiple registrations would gradually reveal kv and v.

Link to this headingExample

https://margin.re/blog/mikrotik-authentication-revealed.aspx#fn1

User Registration:

from cryptopals_lib import * import os, hashlib def generate_key_pair(prime, g): secret_exp = secure_rand_between(2, prime-2) transmit_key = pow(g, secret_exp, prime) return [{"g":g, "p":prime, "key": transmit_key}, {"g":g, "p":prime, "exp": secret_exp}] class SRPServer(): def __init__(self, k, prime, g, hash_obj=hashlib.sha256): self.k = k self.prime = prime # Added: store prime for validation self.g = g # Added: store g self.hash_obj = hash_obj self.database = {} self.auth_database = {} def register_user(self, username, salt, verifier): if username in self.database: raise ValueError(f"Error: User {username} already in database") else: self.database[username] = {"salt": salt, "verifier": verifier} print(f"[Server] Registered user: {username}") def authenticate_init(self, username, client_pub_key): if username not in self.database: raise ValueError(f"Error: {username} is not registered") # SECURITY CHECK: Validate client public key (NEW) if client_pub_key % self.prime == 0: raise ValueError("Invalid client public key (A % N == 0)") user = self.database[username] salt, verifier = user["salt"], user["verifier"] session_pub, session_priv = generate_key_pair(self.prime, self.g) server_key = (((self.k * verifier) % self.prime) + session_pub["key"]) % self.prime self.auth_database[username] = { "client_pub": client_pub_key, "server_key": server_key, "session_priv": session_priv } return salt, server_key def authenticate_verify(self, username, client_proof): if username not in self.database: raise ValueError(f"Error: {username} is not registered") user = self.database[username] salt, verifier = user["salt"], user["verifier"] auth = self.auth_database[username] client_pub = auth["client_pub"] server_key = auth["server_key"] session_priv = auth["session_priv"] # Calculate scrambling parameter u_bytes = self.hash_obj( int_to_bytes(client_pub) + b":" + int_to_bytes(server_key) ).digest() # Compute session key tmp = pow(verifier, bytes_to_int(u_bytes), self.prime) session_s = pow(client_pub * tmp, session_priv["exp"], self.prime) session_k = self.hash_obj(int_to_bytes(session_s)).digest() print(f"[Server] session_key_k: {session_k.hex()}") # Verify client proof h_n_xor_h_g = fixedlen_xor( self.hash_obj(int_to_bytes(self.prime)).digest(), self.hash_obj(int_to_bytes(self.g)).digest() ) proof_verify = self.hash_obj( h_n_xor_h_g + b":" + self.hash_obj(username).digest() + b":" + salt + b":" + int_to_bytes(client_pub) + b":" + int_to_bytes(server_key) + b":" + session_k ).digest() print(f"[Server] client_proof: {client_proof.hex()}") print(f"[Server] client_proof_verify: {proof_verify.hex()}") if client_proof == proof_verify: server_proof = self.hash_obj( int_to_bytes(client_pub) + b":" + client_proof + b":" + session_k ).digest() return server_proof else: # CHANGED: Raise exception instead of just printing raise ValueError("Error: Client verification is invalid") def register_user(username, password, hash_obj, prime, g, k): server = SRPServer(k, prime, g, hash_obj) # Client Register User x = hash_obj(salt + b":" + username + b":" + password).digest() verifier = pow(g, bytes_to_int(x), prime) print(f"[Client] Registering User: {username}, verifier: {hex(verifier)} with salt: {salt.hex()}") server.register_user(username, salt, verifier) return server def authenticate_user(username, password, server, hash_obj, prime, g, k): # Authenticate User client_pub, client_priv = generate_key_pair(prime, g) print(f"\n[Client] Authenticating User: {username} with public key: {hex(client_pub['key'])}") # Get server salt and server key salt_from_server, server_public_key = server.authenticate_init(username, client_pub["key"]) print(f"[Server] Server sending salt: {salt_from_server.hex()} and public key: {hex(server_public_key)}") # SECURITY CHECK: Validate server public key (NEW) if server_public_key % prime == 0: raise ValueError("Invalid server public key (B % N == 0)") # Calculate scrambling parameter u_bytes = hash_obj( int_to_bytes(client_pub["key"]) + b":" + int_to_bytes(server_public_key) ).digest() # Compute session key x = bytes_to_int(hash_obj( salt_from_server + b":" + username + b":" + password ).digest()) session_s = pow( server_public_key - k * pow(g, x, prime), client_priv["exp"] + bytes_to_int(u_bytes) * x, prime ) session_k = hash_obj(int_to_bytes(session_s)).digest() print(f"[Client] session_key_k: {session_k.hex()}") # Generate client proof h_n_xor_h_g = fixedlen_xor( hash_obj(int_to_bytes(prime)).digest(), hash_obj(int_to_bytes(g)).digest() ) client_proof = hash_obj( h_n_xor_h_g + b":" + hash_obj(username).digest() + b":" + salt_from_server + b":" + int_to_bytes(client_pub["key"]) + b":" + int_to_bytes(server_public_key) + b":" + session_k ).digest() # Verify server server_proof = server.authenticate_verify(username, client_proof) server_proof_verify = hash_obj( int_to_bytes(client_pub["key"]) + b":" + client_proof + b":" + session_k ).digest() print(f"[Client] server_proof: {server_proof.hex()}") print(f"[Client] server_proof_verify: {server_proof_verify.hex()}") if server_proof == server_proof_verify: print("\n[+] AUTHENTICATION SUCCESSFUL!") print(f"[+] Session key established: {session_k.hex()}") else: print("\n[!] SERVER VERIFICATION FAILED!") if __name__ == '__main__': # Globals username = b"admin" password = b"Password123" salt = os.urandom(16) hash_obj = hashlib.sha256 # Correct Paramaters from https://datatracker.ietf.org/doc/html/rfc5054#appendix-A prime = int("AC6BDB41324A9A9BF166DE5E1389582FAF72B6651987EE07FC3192943DB56050A37329CBB4A099ED8193E0757767A13DD52312AB4B03310DCD7F48A9DA04FD50E8083969EDB767B0CF6095179A163AB3661A05FBD5FAAAE82918A9962F0B93B855F97993EC975EEAA80D740ADBF4FF747359D041D5C33EA71D281E446B14773BCA97B43A23FB801676BD207A436C6481F1D2B9078717461A5B9D32E688F87748544523B524B0D57D", 16) g = 2 k = bytes_to_int(hash_obj(int_to_bytes(prime) + int_to_bytes(g)).digest()) % prime server = register_user(username, password, hash_obj, prime, g, k) authenticate_user(username, password, server, hash_obj, prime, g, k) try: print(f"\n[Client] Attempting login with wrong password...") authenticate_user(username, b"WrongPassword", server, hash_obj, prime, g, k) except ValueError as e: print(f"[+] Authentication correctly rejected: {e}")